package com.googlecode.connectlet.crypto.cipher.mode.provider;

import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;

import javax.crypto.Cipher;
import javax.crypto.CipherSpi;

import com.googlecode.connectlet.crypto.cipher.CipherFactory;
import com.googlecode.connectlet.crypto.cipher.mode.EcbCipher;
import com.googlecode.connectlet.crypto.cipher.mode.EcbNoPaddingDecipheror;
import com.googlecode.connectlet.crypto.cipher.mode.EcbNoPaddingEncipheror;
import com.googlecode.connectlet.util.Bytes;

public class EcbNoPaddingCipherSpi extends CipherSpi {
	@Deprecated
	@Override
	protected void engineSetMode(String mode) {
		throw new UnsupportedOperationException();
	}

	@Deprecated
	@Override
	protected void engineSetPadding(String padding) {
		throw new UnsupportedOperationException();
	}

	protected CipherFactory cipherFactory;
	protected EcbCipher cipher;
	protected int opmodeOld = 0;

	public EcbNoPaddingCipherSpi(CipherFactory cipherFactory) {
		this.cipherFactory = cipherFactory;
	}

	@Override
	protected int engineGetBlockSize() {
		return cipherFactory.getBlockSize();
	}

	protected EcbNoPaddingEncipheror getEncCipher(CipherFactory factory, byte[] key) {
		return new EcbNoPaddingEncipheror(factory.getEncipheror(key));
	}

	protected EcbNoPaddingDecipheror getDecCipher(CipherFactory factory, byte[] key) {
		return new EcbNoPaddingDecipheror(factory.getDecipheror(key));
	}

	protected void init(int opmode, Key key) {
		if (opmodeOld != opmode) {
			if (opmode == Cipher.ENCRYPT_MODE) {
				cipher = getEncCipher(cipherFactory, key.getEncoded());
			} else {
				cipher = getDecCipher(cipherFactory, key.getEncoded());
			}
			opmodeOld = opmode;
		}
	}

	protected void reset() {
		cipher.init();
	}

	@Override
	protected void engineInit(int opmode, Key key, SecureRandom random) {
		init(opmode, key);
		reset();
	}

	@Override
	protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
			SecureRandom random) throws InvalidKeyException,
			InvalidAlgorithmParameterException {
		engineInit(opmode, key, random);
	}

	@Override
	protected void engineInit(int opmode, Key key, AlgorithmParameters params,
			SecureRandom random) throws InvalidKeyException,
			InvalidAlgorithmParameterException {
		engineInit(opmode, key, random);
	}

	@Override
	protected byte[] engineGetIV() {
		return null;
	}

	@Override
	protected AlgorithmParameters engineGetParameters() {
		return null;
	}

	@Override
	protected int engineGetOutputSize(int inputLen) {
		return cipher.getOutputSize(inputLen);
	}

	@Override
	protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
		byte[] output = new byte[engineGetOutputSize(inputLen)];
		int outputLen = engineUpdate(input, inputOffset, inputLen, output, 0);
		return Bytes.left(output, outputLen);
	}

	@Override
	protected int engineUpdate(byte[] input, int inputOffset, int inputLen,
			byte[] output, int outputOffset) {
		return cipher.update(input, inputOffset, inputLen, output, outputOffset);
	}

	@Override
	protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) {
		byte[] result = cipher.doFinal(input, inputOffset, inputLen);
		reset();
		return result;
	}

	@Override
	protected int engineDoFinal(byte[] input, int inputOffset, int inputLen,
			byte[] output, int outputOffset) {
		int result = cipher.update(input, inputOffset, inputLen, output, outputOffset);
		result += cipher.doFinal(output, outputOffset + result);
		reset();
		return result;
	}
}